package camid.distribution.broker;

import java.io.Serializable;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;

import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;

import camid.distribution.activeobject.ObjectScheduler;
import camid.distribution.events.FutureNotificator;
import camid.distribution.events.InvokerEvent;
import camid.distribution.interceptor.InterceptorChain;
import camid.distribution.interceptor.InvocationEvaluator;
import camid.distribution.interceptor.InvocationInterceptor;
import camid.distribution.lifecycle.ObjectManager;
import camid.distribution.protocol.InvocationDescriptor;
import camid.distribution.protocol.MethodRequestMessage;
import camid.distribution.protocol.MethodResponseMessage;
import camid.distribution.protocol.ResponseStatus;

public class Invoker {

	private ObjectManager objectManager;
	private List<InvocationInterceptor> interceptors;
	private FutureNotificator notificator;
	
	private static final Logger logger = LogManager.getLogger(Invoker.class.getName());
	
	public Invoker(ObjectManager objectManager) 
	{
		this.objectManager = objectManager;
		this.interceptors = Collections.synchronizedList(new ArrayList<InvocationInterceptor> ());
	}
	
	public Invoker(ObjectManager objectManager, FutureNotificator notificator) 
	{
		this.notificator = notificator;
		this.objectManager = objectManager;
		this.interceptors = Collections.synchronizedList(new ArrayList<InvocationInterceptor> ());
	}

	public MethodResponseMessage invoke(MethodRequestMessage req) {
		
		InvocationDescriptor descriptor = req.getDescriptor();
		
		MethodResponseMessage responseMsg = new MethodResponseMessage();
		
		InvokerEvent ie = new InvokerEvent();
		ie.setObjectId(descriptor.getObjectId());
		ie.setEntityClass(descriptor.getEntityClass());
		ie.setMethodName(descriptor.getMethod());
		
		logger.info("Invoking " + descriptor.getEntityClass() 
				+ "/" + descriptor.getObjectId() + "/" + descriptor.getMethod());
//		try 
//		{
//			ie.startTimer();
//			
//			for(InvocationInterceptor interceptor : interceptors)
//			{
//					interceptor.beforeInvocation(req);
//			}
//			
//			Object response = evaluateInvocation(descriptor);
//			
//			if( response instanceof Exception )
//			{
//				responseMsg.setResponse((Serializable)response);
//				responseMsg.setStatus(ResponseStatus.SERVICE_EXCEPTION);
//			}
//			else
//			{
//				responseMsg.setResponse((Serializable)response);
//				responseMsg.setStatus(ResponseStatus.SUCCESS);
//			}
//			
//		}
//		catch(Exception e)
//		{
//			responseMsg.setResponse(e);
//			responseMsg.setStatus(ResponseStatus.SERVICE_EXCEPTION);
//		}
		
		ie.startTimer();
		try {
			InvokerEvaluator evaluator = new InvokerEvaluator(this.objectManager);
			InterceptorChain interceptorChain = new InterceptorChain(req, interceptors, evaluator);
			responseMsg = interceptorChain.proceed();
		} catch (Exception e) {
			responseMsg.setResponse(e);
			responseMsg.setStatus(ResponseStatus.SERVICE_EXCEPTION);
		}
		
		ie.stopTimer();
		if(notificator != null)
		{
			notificator.notifyEvent(ie);
		}
		
//		synchronized(interceptors)
//		{
//			for(InvocationInterceptor interceptor : interceptors)
//			{
//				try 
//				{
//					interceptor.afterInvocation(req, responseMsg);
//				} 
//				catch (Exception e) 
//				{
//					responseMsg.setResponse(e);
//					responseMsg.setStatus(ResponseStatus.SERVICE_EXCEPTION);
//				}
//			}
//		}
		
		return responseMsg;
	}
	
	private Object evaluateInvocation( InvocationDescriptor descriptor )
	{
		Object response = null;
		try {
		
			ObjectScheduler scheduler = null;
			String objectId = descriptor.getObjectId();
			if( !objectManager.hasObject(objectId) )
			{
				String entityClassName = descriptor.getEntityClass();
				Class<?> entityClass = Class.forName(entityClassName);
				scheduler = objectManager.newObject(objectId, entityClass);
			}
			else
			{
				scheduler = objectManager.getObject(objectId);
			}
			
			if( scheduler != null )
			{
				String method = descriptor.getMethod();
				Object[] params = descriptor.getParams();
				try {
					scheduler.acquire();
					response = scheduler.submitTask(method, params);
				}
				finally {
					scheduler.release();
				}
//				response = future.get();
			}
		}
		catch (Exception e) {
			e.printStackTrace();
			response = e;
		}
		
		return response;
	}

	public void addInterceptor(InvocationInterceptor invocationInterceptor) {
		this.interceptors.add(invocationInterceptor);
		
	}
	
	private static class InvokerEvaluator implements InvocationEvaluator {
		
		private ObjectManager objectManager;
		
		public InvokerEvaluator(ObjectManager objectManager) {
			this.objectManager = objectManager;
		}

		@Override
		public MethodResponseMessage evaluate(
				MethodRequestMessage requestMessage) {
			
			MethodResponseMessage responseMsg = new MethodResponseMessage();
			Map<String,Object> context = requestMessage.getInvocationContext();
			responseMsg.setInvocationContext(context);
			
			InvocationDescriptor descriptor = requestMessage.getDescriptor();
			Object response = null;
			try {
			
				ObjectScheduler scheduler = null;
				String objectId = descriptor.getObjectId();
				if(!objectManager.hasObject(objectId))
				{
					String entityClassName = descriptor.getEntityClass();
					Class<?> entityClass = Class.forName(entityClassName);
					scheduler = objectManager.newObject(objectId, entityClass);
				}
				else
				{
					scheduler = objectManager.getObject(objectId);
				}
				
				if( scheduler != null )
				{
					String method = descriptor.getMethod();
					Object[] params = descriptor.getParams();
					try {
						scheduler.acquire();
						response = scheduler.submitTask(method, params);
					}
					finally {
						scheduler.release();
					}
//					response = future.get();
					if( response instanceof Exception )
					{
						responseMsg.setResponse((Serializable)response);
						responseMsg.setStatus(ResponseStatus.SERVICE_EXCEPTION);
					}
					else
					{
						responseMsg.setResponse((Serializable)response);
						responseMsg.setStatus(ResponseStatus.SUCCESS);
					}
				}
			} catch (Exception e) {
				e.printStackTrace();
				response = e;
				responseMsg.setResponse((Serializable)response);
				responseMsg.setStatus(ResponseStatus.SERVICE_EXCEPTION);
			}
			
			
			return responseMsg;
		}
		
	}

}
